//! See [`CargoWorkspace`].

use std::{borrow::Cow, ops, str::from_utf8};

use anyhow::Context;
use base_db::Env;
use cargo_metadata::{CargoOpt, MetadataCommand, PackageId};
use la_arena::{Arena, Idx};
use paths::{AbsPath, AbsPathBuf, Utf8Path, Utf8PathBuf};
use rustc_hash::{FxHashMap, FxHashSet};
use serde_derive::Deserialize;
use serde_json::from_value;
use span::Edition;
use stdx::process::spawn_with_streaming_output;
use toolchain::{NO_RUSTUP_AUTO_INSTALL_ENV, Tool};
use triomphe::Arc;

use crate::{
    CfgOverrides, InvocationStrategy, ManifestPath, Sysroot, cargo_config_file::make_lockfile_copy,
};

pub(crate) const MINIMUM_TOOLCHAIN_VERSION_SUPPORTING_LOCKFILE_PATH: semver::Version =
    semver::Version {
        major: 1,
        minor: 82,
        patch: 0,
        pre: semver::Prerelease::EMPTY,
        build: semver::BuildMetadata::EMPTY,
    };

/// [`CargoWorkspace`] represents the logical structure of, well, a Cargo
/// workspace. It pretty closely mirrors `cargo metadata` output.
///
/// Note that internally, rust-analyzer uses a different structure:
/// `CrateGraph`. `CrateGraph` is lower-level: it knows only about the crates,
/// while this knows about `Packages` & `Targets`: purely cargo-related
/// concepts.
///
/// We use absolute paths here, `cargo metadata` guarantees to always produce
/// abs paths.
#[derive(Debug, Clone, Eq, PartialEq)]
pub struct CargoWorkspace {
    packages: Arena<PackageData>,
    targets: Arena<TargetData>,
    workspace_root: AbsPathBuf,
    target_directory: AbsPathBuf,
    manifest_path: ManifestPath,
    is_virtual_workspace: bool,
    /// Whether this workspace represents the sysroot workspace.
    is_sysroot: bool,
    /// Environment variables set in the `.cargo/config` file and the extraEnv
    /// configuration option.
    env: Env,
    requires_rustc_private: bool,
}

impl ops::Index<Package> for CargoWorkspace {
    type Output = PackageData;
    fn index(&self, index: Package) -> &PackageData {
        &self.packages[index]
    }
}

impl ops::Index<Target> for CargoWorkspace {
    type Output = TargetData;
    fn index(&self, index: Target) -> &TargetData {
        &self.targets[index]
    }
}

/// Describes how to set the rustc source directory.
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum RustLibSource {
    /// Explicit path for the rustc source directory.
    Path(AbsPathBuf),
    /// Try to automatically detect where the rustc source directory is.
    Discover,
}

#[derive(Clone, Debug, PartialEq, Eq)]
pub enum CargoFeatures {
    All,
    Selected {
        /// List of features to activate.
        features: Vec<String>,
        /// Do not activate the `default` feature.
        no_default_features: bool,
    },
}

impl Default for CargoFeatures {
    fn default() -> Self {
        CargoFeatures::Selected { features: vec![], no_default_features: false }
    }
}

#[derive(Clone, Debug, Default, PartialEq, Eq)]
pub enum TargetDirectoryConfig {
    #[default]
    None,
    UseSubdirectory,
    Directory(Utf8PathBuf),
}

impl TargetDirectoryConfig {
    pub fn target_dir<'a>(
        &'a self,
        ws_target_dir: Option<&'a Utf8Path>,
    ) -> Option<Cow<'a, Utf8Path>> {
        match self {
            TargetDirectoryConfig::None => None,
            TargetDirectoryConfig::UseSubdirectory => {
                Some(Cow::Owned(ws_target_dir?.join("rust-analyzer")))
            }
            TargetDirectoryConfig::Directory(dir) => Some(Cow::Borrowed(dir)),
        }
    }
}

#[derive(Default, Clone, Debug, PartialEq, Eq)]
pub struct CargoConfig {
    /// Whether to pass `--all-targets` to cargo invocations.
    pub all_targets: bool,
    /// List of features to activate.
    pub features: CargoFeatures,
    /// rustc target
    pub target: Option<String>,
    /// Sysroot loading behavior
    pub sysroot: Option<RustLibSource>,
    pub sysroot_src: Option<AbsPathBuf>,
    /// rustc private crate source
    pub rustc_source: Option<RustLibSource>,
    /// Extra includes to add to the VFS.
    pub extra_includes: Vec<AbsPathBuf>,
    pub cfg_overrides: CfgOverrides,
    /// Invoke `cargo check` through the RUSTC_WRAPPER.
    pub wrap_rustc_in_build_scripts: bool,
    /// The command to run instead of `cargo check` for building build scripts.
    pub run_build_script_command: Option<Vec<String>>,
    /// Extra args to pass to the cargo command.
    pub extra_args: Vec<String>,
    /// Extra env vars to set when invoking the cargo command
    pub extra_env: FxHashMap<String, Option<String>>,
    pub invocation_strategy: InvocationStrategy,
    /// Optional path to use instead of `target` when building
    pub target_dir_config: TargetDirectoryConfig,
    /// Gate `#[test]` behind `#[cfg(test)]`
    pub set_test: bool,
    /// Load the project without any dependencies
    pub no_deps: bool,
}

pub type Package = Idx<PackageData>;

pub type Target = Idx<TargetData>;

/// Information associated with a cargo crate
#[derive(Debug, Clone, Eq, PartialEq)]
pub struct PackageData {
    /// Version given in the `Cargo.toml`
    pub version: semver::Version,
    /// Name as given in the `Cargo.toml`
    pub name: String,
    /// Repository as given in the `Cargo.toml`
    pub repository: Option<String>,
    /// Path containing the `Cargo.toml`
    pub manifest: ManifestPath,
    /// Targets provided by the crate (lib, bin, example, test, ...)
    pub targets: Vec<Target>,
    /// Does this package come from the local filesystem (and is editable)?
    pub is_local: bool,
    /// Whether this package is a member of the workspace
    pub is_member: bool,
    /// List of packages this package depends on
    pub dependencies: Vec<PackageDependency>,
    /// Rust edition for this package
    pub edition: Edition,
    /// Features provided by the crate, mapped to the features required by that feature.
    pub features: FxHashMap<String, Vec<String>>,
    /// List of features enabled on this package
    pub active_features: Vec<String>,
    /// Package id
    pub id: Arc<PackageId>,
    /// Authors as given in the `Cargo.toml`
    pub authors: Vec<String>,
    /// Description as given in the `Cargo.toml`
    pub description: Option<String>,
    /// Homepage as given in the `Cargo.toml`
    pub homepage: Option<String>,
    /// License as given in the `Cargo.toml`
    pub license: Option<String>,
    /// License file as given in the `Cargo.toml`
    pub license_file: Option<Utf8PathBuf>,
    /// Readme file as given in the `Cargo.toml`
    pub readme: Option<Utf8PathBuf>,
    /// Rust version as given in the `Cargo.toml`
    pub rust_version: Option<semver::Version>,
    /// The contents of [package.metadata.rust-analyzer]
    pub metadata: RustAnalyzerPackageMetaData,
    /// If this package is a member of the workspace, store all direct and transitive
    /// dependencies as long as they are workspace members, to track dependency relationships
    /// between members.
    pub all_member_deps: Option<FxHashSet<Package>>,
}

#[derive(Deserialize, Default, Debug, Clone, Eq, PartialEq)]
pub struct RustAnalyzerPackageMetaData {
    pub rustc_private: bool,
}

#[derive(Debug, Clone, Eq, PartialEq)]
pub struct PackageDependency {
    pub pkg: Package,
    pub name: String,
    pub kind: DepKind,
}

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum DepKind {
    /// Available to the library, binary, and dev targets in the package (but not the build script).
    Normal,
    /// Available only to test and bench targets (and the library target, when built with `cfg(test)`).
    Dev,
    /// Available only to the build script target.
    Build,
}

impl DepKind {
    fn iter(list: &[cargo_metadata::DepKindInfo]) -> impl Iterator<Item = Self> {
        let mut dep_kinds = [None; 3];
        if list.is_empty() {
            dep_kinds[0] = Some(Self::Normal);
        }
        for info in list {
            match info.kind {
                cargo_metadata::DependencyKind::Normal => dep_kinds[0] = Some(Self::Normal),
                cargo_metadata::DependencyKind::Development => dep_kinds[1] = Some(Self::Dev),
                cargo_metadata::DependencyKind::Build => dep_kinds[2] = Some(Self::Build),
                cargo_metadata::DependencyKind::Unknown => continue,
            }
        }
        dep_kinds.into_iter().flatten()
    }
}

/// Information associated with a package's target
#[derive(Debug, Clone, Eq, PartialEq)]
pub struct TargetData {
    /// Package that provided this target
    pub package: Package,
    /// Name as given in the `Cargo.toml` or generated from the file name
    pub name: String,
    /// Path to the main source file of the target
    pub root: AbsPathBuf,
    /// Kind of target
    pub kind: TargetKind,
    /// Required features of the target without which it won't build
    pub required_features: Vec<String>,
}

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum TargetKind {
    Bin,
    /// Any kind of Cargo lib crate-type (dylib, rlib, proc-macro, ...).
    Lib {
        /// Is this target a proc-macro
        is_proc_macro: bool,
    },
    Example,
    Test,
    Bench,
    /// Cargo calls this kind `custom-build`
    BuildScript,
    Other,
}

impl TargetKind {
    pub fn new(kinds: &[cargo_metadata::TargetKind]) -> TargetKind {
        for kind in kinds {
            return match kind {
                cargo_metadata::TargetKind::Bin => TargetKind::Bin,
                cargo_metadata::TargetKind::Test => TargetKind::Test,
                cargo_metadata::TargetKind::Bench => TargetKind::Bench,
                cargo_metadata::TargetKind::Example => TargetKind::Example,
                cargo_metadata::TargetKind::CustomBuild => TargetKind::BuildScript,
                cargo_metadata::TargetKind::ProcMacro => TargetKind::Lib { is_proc_macro: true },
                cargo_metadata::TargetKind::Lib
                | cargo_metadata::TargetKind::DyLib
                | cargo_metadata::TargetKind::CDyLib
                | cargo_metadata::TargetKind::StaticLib
                | cargo_metadata::TargetKind::RLib => TargetKind::Lib { is_proc_macro: false },
                _ => continue,
            };
        }
        TargetKind::Other
    }

    pub fn is_executable(self) -> bool {
        matches!(self, TargetKind::Bin | TargetKind::Example)
    }

    pub fn is_proc_macro(self) -> bool {
        matches!(self, TargetKind::Lib { is_proc_macro: true })
    }

    /// If this is a valid cargo target, returns the name cargo uses in command line arguments
    /// and output, otherwise None.
    /// <https://docs.rs/cargo_metadata/latest/cargo_metadata/enum.TargetKind.html>
    pub fn as_cargo_target(self) -> Option<&'static str> {
        match self {
            TargetKind::Bin => Some("bin"),
            TargetKind::Lib { is_proc_macro: true } => Some("proc-macro"),
            TargetKind::Lib { is_proc_macro: false } => Some("lib"),
            TargetKind::Example => Some("example"),
            TargetKind::Test => Some("test"),
            TargetKind::Bench => Some("bench"),
            TargetKind::BuildScript => Some("custom-build"),
            TargetKind::Other => None,
        }
    }
}

#[derive(Default, Clone, Debug, PartialEq, Eq)]
pub struct CargoMetadataConfig {
    /// List of features to activate.
    pub features: CargoFeatures,
    /// rustc targets
    pub targets: Vec<String>,
    /// Extra args to pass to the cargo command.
    pub extra_args: Vec<String>,
    /// Extra env vars to set when invoking the cargo command
    pub extra_env: FxHashMap<String, Option<String>>,
    /// What kind of metadata are we fetching: workspace, rustc, or sysroot.
    pub kind: &'static str,
    /// The toolchain version, if known.
    /// Used to conditionally enable unstable cargo features.
    pub toolchain_version: Option<semver::Version>,
}

// Deserialize helper for the cargo metadata
#[derive(Deserialize, Default)]
struct PackageMetadata {
    #[serde(rename = "rust-analyzer")]
    rust_analyzer: Option<RustAnalyzerPackageMetaData>,
}

impl CargoWorkspace {
    pub fn new(
        mut meta: cargo_metadata::Metadata,
        ws_manifest_path: ManifestPath,
        cargo_env: Env,
        is_sysroot: bool,
    ) -> CargoWorkspace {
        let mut pkg_by_id = FxHashMap::default();
        let mut packages = Arena::default();
        let mut targets = Arena::default();

        let ws_members = &meta.workspace_members;

        let workspace_root = AbsPathBuf::assert(meta.workspace_root);
        let target_directory = AbsPathBuf::assert(meta.target_directory);
        let mut is_virtual_workspace = true;
        let mut requires_rustc_private = false;

        let mut members = FxHashSet::default();

        meta.packages.sort_by(|a, b| a.id.cmp(&b.id));
        for meta_pkg in meta.packages {
            let cargo_metadata::Package {
                name,
                version,
                id,
                source,
                targets: meta_targets,
                features,
                manifest_path,
                repository,
                edition,
                metadata,
                authors,
                description,
                homepage,
                license,
                license_file,
                readme,
                rust_version,
                ..
            } = meta_pkg;
            let id = Arc::new(id);
            let meta = from_value::<PackageMetadata>(metadata).unwrap_or_default();
            let edition = match edition {
                cargo_metadata::Edition::E2015 => Edition::Edition2015,
                cargo_metadata::Edition::E2018 => Edition::Edition2018,
                cargo_metadata::Edition::E2021 => Edition::Edition2021,
                cargo_metadata::Edition::E2024 => Edition::Edition2024,
                _ => {
                    tracing::error!("Unsupported edition `{:?}`", edition);
                    Edition::CURRENT
                }
            };
            // We treat packages without source as "local" packages. That includes all members of
            // the current workspace, as well as any path dependency outside the workspace.
            let is_local = source.is_none();
            let is_member = ws_members.contains(&id);

            let manifest = ManifestPath::try_from(AbsPathBuf::assert(manifest_path)).unwrap();
            is_virtual_workspace &= manifest != ws_manifest_path;
            let pkg = packages.alloc(PackageData {
                id: id.clone(),
                name: name.to_string(),
                version,
                manifest: manifest.clone(),
                targets: Vec::new(),
                is_local,
                is_member,
                edition,
                repository,
                authors,
                description,
                homepage,
                license,
                license_file,
                readme,
                rust_version,
                dependencies: Vec::new(),
                features: features.into_iter().collect(),
                active_features: Vec::new(),
                metadata: meta.rust_analyzer.unwrap_or_default(),
                all_member_deps: None,
            });
            if is_member {
                members.insert(pkg);
            }
            let pkg_data = &mut packages[pkg];
            requires_rustc_private |= pkg_data.metadata.rustc_private;
            pkg_by_id.insert(id, pkg);
            for meta_tgt in meta_targets {
                let cargo_metadata::Target { name, kind, required_features, src_path, .. } =
                    meta_tgt;
                let kind = TargetKind::new(&kind);
                let tgt = targets.alloc(TargetData {
                    package: pkg,
                    name,
                    root: if kind == TargetKind::Bin
                        && manifest.extension().is_some_and(|ext| ext == "rs")
                    {
                        // cargo strips the script part of a cargo script away and places the
                        // modified manifest file into a special target dir which is then used as
                        // the source path. We don't want that, we want the original here so map it
                        // back
                        manifest.clone().into()
                    } else {
                        AbsPathBuf::assert(src_path)
                    },
                    kind,
                    required_features,
                });
                pkg_data.targets.push(tgt);
            }
        }
        for mut node in meta.resolve.map_or_else(Vec::new, |it| it.nodes) {
            let &source = pkg_by_id.get(&node.id).unwrap();
            node.deps.sort_by(|a, b| a.pkg.cmp(&b.pkg));
            let dependencies = node
                .deps
                .iter()
                .flat_map(|dep| DepKind::iter(&dep.dep_kinds).map(move |kind| (dep, kind)));
            for (dep_node, kind) in dependencies {
                let &pkg = pkg_by_id.get(&dep_node.pkg).unwrap();
                let dep = PackageDependency { name: dep_node.name.to_string(), pkg, kind };
                packages[source].dependencies.push(dep);
            }
            packages[source]
                .active_features
                .extend(node.features.into_iter().map(|it| it.to_string()));
        }

        fn saturate_all_member_deps(
            packages: &mut Arena<PackageData>,
            to_visit: Package,
            visited: &mut FxHashSet<Package>,
            members: &FxHashSet<Package>,
        ) {
            let pkg_data = &mut packages[to_visit];

            if !visited.insert(to_visit) {
                return;
            }

            let deps: Vec<_> = pkg_data
                .dependencies
                .iter()
                .filter_map(|dep| {
                    let pkg = dep.pkg;
                    if members.contains(&pkg) { Some(pkg) } else { None }
                })
                .collect();

            let mut all_member_deps = FxHashSet::from_iter(deps.iter().copied());
            for dep in deps {
                saturate_all_member_deps(packages, dep, visited, members);
                if let Some(transitives) = &packages[dep].all_member_deps {
                    all_member_deps.extend(transitives);
                }
            }

            packages[to_visit].all_member_deps = Some(all_member_deps);
        }

        let mut visited = FxHashSet::default();
        for member in members.iter() {
            saturate_all_member_deps(&mut packages, *member, &mut visited, &members);
        }

        CargoWorkspace {
            packages,
            targets,
            workspace_root,
            target_directory,
            manifest_path: ws_manifest_path,
            is_virtual_workspace,
            requires_rustc_private,
            is_sysroot,
            env: cargo_env,
        }
    }

    pub fn packages(&self) -> impl ExactSizeIterator<Item = Package> + '_ {
        self.packages.iter().map(|(id, _pkg)| id)
    }

    pub fn target_by_root(&self, root: &AbsPath) -> Option<Target> {
        self.packages()
            .filter(|&pkg| self[pkg].is_member)
            .find_map(|pkg| self[pkg].targets.iter().find(|&&it| self[it].root == root))
            .copied()
    }

    pub fn workspace_root(&self) -> &AbsPath {
        &self.workspace_root
    }

    pub fn manifest_path(&self) -> &ManifestPath {
        &self.manifest_path
    }

    pub fn target_directory(&self) -> &AbsPath {
        &self.target_directory
    }

    pub fn package_flag(&self, package: &PackageData) -> String {
        if self.is_unique(&package.name) {
            package.name.clone()
        } else {
            format!("{}:{}", package.name, package.version)
        }
    }

    pub fn parent_manifests(&self, manifest_path: &ManifestPath) -> Option<Vec<ManifestPath>> {
        let mut found = false;
        let parent_manifests = self
            .packages()
            .filter_map(|pkg| {
                if !found && &self[pkg].manifest == manifest_path {
                    found = true
                }
                self[pkg].dependencies.iter().find_map(|dep| {
                    (&self[dep.pkg].manifest == manifest_path).then(|| self[pkg].manifest.clone())
                })
            })
            .collect::<Vec<ManifestPath>>();

        // some packages has this pkg as dep. return their manifests
        if !parent_manifests.is_empty() {
            return Some(parent_manifests);
        }

        // this pkg is inside this cargo workspace, fallback to workspace root
        if found {
            return Some(vec![
                ManifestPath::try_from(self.workspace_root().join("Cargo.toml")).ok()?,
            ]);
        }

        // not in this workspace
        None
    }

    /// Returns the union of the features of all member crates in this workspace.
    pub fn workspace_features(&self) -> FxHashSet<String> {
        self.packages()
            .filter_map(|package| {
                let package = &self[package];
                if package.is_member {
                    Some(package.features.keys().cloned().chain(
                        package.features.keys().map(|key| format!("{}/{key}", package.name)),
                    ))
                } else {
                    None
                }
            })
            .flatten()
            .collect()
    }

    fn is_unique(&self, name: &str) -> bool {
        self.packages.iter().filter(|(_, v)| v.name == name).count() == 1
    }

    pub fn is_virtual_workspace(&self) -> bool {
        self.is_virtual_workspace
    }

    pub fn env(&self) -> &Env {
        &self.env
    }

    pub fn is_sysroot(&self) -> bool {
        self.is_sysroot
    }

    pub fn requires_rustc_private(&self) -> bool {
        self.requires_rustc_private
    }
}

pub(crate) struct FetchMetadata {
    command: cargo_metadata::MetadataCommand,
    #[expect(dead_code)]
    manifest_path: ManifestPath,
    lockfile_path: Option<Utf8PathBuf>,
    #[expect(dead_code)]
    kind: &'static str,
    no_deps: bool,
    no_deps_result: anyhow::Result<cargo_metadata::Metadata>,
    other_options: Vec<String>,
}

impl FetchMetadata {
    /// Builds a command to fetch metadata for the given `cargo_toml` manifest.
    ///
    /// Performs a lightweight pre-fetch using the `--no-deps` option,
    /// available via [`FetchMetadata::no_deps_metadata`], to gather basic
    /// information such as the `target-dir`.
    ///
    /// The provided sysroot is used to set the `RUSTUP_TOOLCHAIN`
    /// environment variable when invoking Cargo, ensuring that the
    /// rustup proxy selects the correct toolchain.
    pub(crate) fn new(
        cargo_toml: &ManifestPath,
        current_dir: &AbsPath,
        config: &CargoMetadataConfig,
        sysroot: &Sysroot,
        no_deps: bool,
    ) -> Self {
        let cargo = sysroot.tool(Tool::Cargo, current_dir, &config.extra_env);
        let mut command = MetadataCommand::new();
        command.env(NO_RUSTUP_AUTO_INSTALL_ENV.0, NO_RUSTUP_AUTO_INSTALL_ENV.1);
        command.cargo_path(cargo.get_program());
        cargo.get_envs().for_each(|(var, val)| _ = command.env(var, val.unwrap_or_default()));
        command.manifest_path(cargo_toml.to_path_buf());
        match &config.features {
            CargoFeatures::All => {
                command.features(CargoOpt::AllFeatures);
            }
            CargoFeatures::Selected { features, no_default_features } => {
                if *no_default_features {
                    command.features(CargoOpt::NoDefaultFeatures);
                }
                if !features.is_empty() {
                    command.features(CargoOpt::SomeFeatures(features.clone()));
                }
            }
        }
        command.current_dir(current_dir);

        let mut other_options = vec![];
        // cargo metadata only supports a subset of flags of what cargo usually accepts, and usually
        // the only relevant flags for metadata here are unstable ones, so we pass those along
        // but nothing else
        let mut extra_args = config.extra_args.iter();
        while let Some(arg) = extra_args.next() {
            if arg == "-Z"
                && let Some(arg) = extra_args.next()
            {
                other_options.push("-Z".to_owned());
                other_options.push(arg.to_owned());
            }
        }

        let mut lockfile_path = None;
        if cargo_toml.is_rust_manifest() {
            other_options.push("-Zscript".to_owned());
        } else if config
            .toolchain_version
            .as_ref()
            .is_some_and(|v| *v >= MINIMUM_TOOLCHAIN_VERSION_SUPPORTING_LOCKFILE_PATH)
        {
            lockfile_path = Some(<_ as AsRef<Utf8Path>>::as_ref(cargo_toml).with_extension("lock"));
        }

        if !config.targets.is_empty() {
            other_options.extend(
                config.targets.iter().flat_map(|it| ["--filter-platform".to_owned(), it.clone()]),
            );
        }

        command.other_options(other_options.clone());

        // Pre-fetch basic metadata using `--no-deps`, which:
        // - avoids fetching registries like crates.io,
        // - skips dependency resolution and does not modify lockfiles,
        // - and thus doesn't require progress reporting or copying lockfiles.
        //
        // Useful as a fast fallback to extract info like `target-dir`.
        let cargo_command;
        let no_deps_result = if no_deps {
            command.no_deps();
            cargo_command = command.cargo_command();
            command.exec()
        } else {
            let mut no_deps_command = command.clone();
            no_deps_command.no_deps();
            cargo_command = no_deps_command.cargo_command();
            no_deps_command.exec()
        }
        .with_context(|| format!("Failed to run `{cargo_command:?}`"));

        Self {
            manifest_path: cargo_toml.clone(),
            command,
            lockfile_path,
            kind: config.kind,
            no_deps,
            no_deps_result,
            other_options,
        }
    }

    /// Executes the metadata-fetching command.
    ///
    /// A successful result may still contain a metadata error if the full fetch failed,
    /// but the fallback `--no-deps` pre-fetch succeeded during command construction.
    pub(crate) fn exec(
        self,
        locked: bool,
        progress: &dyn Fn(String),
    ) -> anyhow::Result<(cargo_metadata::Metadata, Option<anyhow::Error>)> {
        let Self {
            mut command,
            manifest_path: _,
            lockfile_path,
            kind: _,
            no_deps,
            no_deps_result,
            mut other_options,
        } = self;

        if no_deps {
            return no_deps_result.map(|m| (m, None));
        }

        let mut using_lockfile_copy = false;
        let mut _temp_dir_guard;
        if let Some(lockfile) = lockfile_path
            && let Some((temp_dir, target_lockfile)) = make_lockfile_copy(&lockfile)
        {
            _temp_dir_guard = temp_dir;
            other_options.push("--lockfile-path".to_owned());
            other_options.push(target_lockfile.to_string());
            using_lockfile_copy = true;
        }
        if using_lockfile_copy || other_options.iter().any(|it| it.starts_with("-Z")) {
            command.env("__CARGO_TEST_CHANNEL_OVERRIDE_DO_NOT_USE_THIS", "nightly");
            other_options.push("-Zunstable-options".to_owned());
        }
        // No need to lock it if we copied the lockfile, we won't modify the original after all/
        // This way cargo cannot error out on us if the lockfile requires updating.
        if !using_lockfile_copy && locked {
            other_options.push("--locked".to_owned());
        }
        command.other_options(other_options);

        progress("cargo metadata: started".to_owned());

        let res = (|| -> anyhow::Result<(_, _)> {
            let mut errored = false;
            tracing::debug!("Running `{:?}`", command.cargo_command());
            let output =
                spawn_with_streaming_output(command.cargo_command(), &mut |_| (), &mut |line| {
                    errored = errored || line.starts_with("error") || line.starts_with("warning");
                    if errored {
                        progress("cargo metadata: ?".to_owned());
                        return;
                    }
                    progress(format!("cargo metadata: {line}"));
                })?;
            if !output.status.success() {
                progress(format!("cargo metadata: failed {}", output.status));
                let error = cargo_metadata::Error::CargoMetadata {
                    stderr: String::from_utf8(output.stderr)?,
                }
                .into();
                if !no_deps {
                    // If we failed to fetch metadata with deps, return pre-fetched result without them.
                    // This makes r-a still work partially when offline.
                    if let Ok(metadata) = no_deps_result {
                        tracing::warn!(
                            ?error,
                            "`cargo metadata` failed and returning succeeded result with `--no-deps`"
                        );
                        return Ok((metadata, Some(error)));
                    }
                }
                return Err(error);
            }
            let stdout = from_utf8(&output.stdout)?
                .lines()
                .find(|line| line.starts_with('{'))
                .ok_or(cargo_metadata::Error::NoJson)?;
            Ok((cargo_metadata::MetadataCommand::parse(stdout)?, None))
        })()
        .with_context(|| format!("Failed to run `{:?}`", command.cargo_command()));
        progress("cargo metadata: finished".to_owned());
        res
    }
}
